/*
known issues:
1. in darwin, set_pty_echo() does not work
*/

#include "jc_pty.h"
// standard C++ lib
#include <cstring>
#include <cstdlib>
#include <thread>
// common linux/unix lib
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <sys/ioctl.h>

#include <sys/stat.h>
#include <csignal>

#ifdef __linux__
#include <pty.h>
#include <sys/epoll.h>
#elif defined(__APPLE__) || defined(__MACH__)
#include <util.h>
#include <sys/event.h>
#else
#error "Unsupported platform"
#endif

int pid = 0, fd = 0;
std::thread _reading_threading;
void (*out_from_pty)(char *buf, int len) = NULL;
#if __linux__
const char *shell = "/bin/bash";
#else
const char *shell = "/bin/zsh";
#endif

int _read_from_pty(int fd);

void set_pty_size(unsigned short rows, unsigned short cols)
{
    if (fd <= 0)
    {
        return;
    }
    struct winsize size = {rows, cols, 0, 0};
    ioctl(fd, TIOCSWINSZ, &size);
}

void set_pty_echo(bool enable)
{
    if (fd <= 0)
    {
        return;
    }
    struct termios term;
    tcgetattr(fd, &term);
    if (enable)
    {
        term.c_lflag |= (ECHO);
    }
    else
    {
        term.c_lflag &= ~(ECHO);
    }
    tcsetattr(fd, TCSANOW, &term);
}

#if __linux__
void start_pty(void (*out_function)(char *buf, int len))
{
    if (!out_function)
    {
        return;
    }
    out_from_pty = out_function;
    pid = forkpty(&fd, NULL, NULL, NULL);
    if (pid == 0)
    {

        execlp(shell, shell, NULL);
        exit(0);
    }
    struct termios attrs;
    tcgetattr(fd, &attrs);
    attrs.c_lflag &= ~(ECHO);
    tcsetattr(fd, TCSANOW, &attrs);
    _reading_threading = std::thread(_read_from_pty, fd);
    _reading_threading.detach();
}
#else
void start_pty(void (*out_function)(char *buf, int len))
{
    if (!out_function)
    {
        return;
    }
    out_from_pty = out_function;
    int master_fd, slave_fd;
    // start a pty
    if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) < 0)
    {
        perror("openpty");
        exit(1);
    }
    pid = fork();
    if (pid == -1)
    {
        perror("fork");
        exit(1);
    }
    if (pid == 0)
    { // child process
        close(master_fd);
        dup2(slave_fd, STDIN_FILENO);
        dup2(slave_fd, STDOUT_FILENO);
        dup2(slave_fd, STDERR_FILENO);

        // set raw mode and disable echo
        struct termios termios_p;
        tcgetattr(slave_fd, &termios_p);
        cfmakeraw(&termios_p);
        termios_p.c_lflag &= ~(ECHO);
        tcsetattr(slave_fd, TCSANOW, &termios_p);

        // exec shell
        execlp(shell, shell, NULL);
        perror("execlp");
        exit(1);
    }
    // master process
    close(slave_fd); // 关闭从端文件描述符
    // 读取输入并将其发送到子进程
    fd = master_fd;
    _reading_threading = std::thread([master_fd]()
                                     { _read_from_pty(master_fd); });
    set_pty_echo(false);
    _reading_threading.detach();
}
#endif
#if __linux__
int _read_from_pty(int fd)
{
    int epoll_fd = epoll_create1(0);
    if (epoll_fd == -1)
    {
        return -__LINE__;
    }
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = fd;

    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event) == -1)
    {
        return -__LINE__;
    }
    const int MAX_EVENTS = 8;
    struct epoll_event events[MAX_EVENTS];
    const int buf_len = 1024 * 3;
    static char buf[buf_len];
    while (1)
    {
        int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        if (num_events == -1)
        {
            return -__LINE__;
        }
        for (int i = 0; i < num_events; i++)
        {
            if (events[i].events & EPOLLIN)
            {

                int num_read = read(events[i].data.fd, buf, buf_len);
                if (num_read == -1)
                {
                    return -__LINE__;
                }
                if (num_read == 0)
                {
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL) == -1)
                    {
                        return -__LINE__;
                    }
                }
                else
                {
                    if (out_from_pty)
                        out_from_pty(buf, num_read);
                }
            }
        }
    }
    close(epoll_fd);
    return 0;
}
#else
int _read_from_pty(int fd)
{
    int kq = kqueue();
    if (kq == -1)
    {
        return -__LINE__;
    }
    struct kevent event;
    EV_SET(&event, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);

    if (kevent(kq, &event, 1, NULL, 0, NULL) == -1)
    {
        return -__LINE__;
    }
    const int MAX_EVENTS = 8;
    struct kevent events[MAX_EVENTS];
    const int buf_len = 1024 * 3;
    static char buf[buf_len];
    while (1)
    {
        int num_events = kevent(kq, NULL, 0, events, MAX_EVENTS, NULL);
        if (num_events == -1)
        {
            break;
        }
        for (int i = 0; i < num_events; i++)
        {
            if (events[i].filter == EVFILT_READ)
            {
                int num_read = read(events[i].ident, buf, buf_len);
                if (num_read == -1)
                {
                    break;
                }
                if (num_read == 0)
                {
                    if (kevent(kq, &event, 1, NULL, 0, NULL) == -1)
                    {
                        break;
                    }
                }
                else
                {
                    if (out_from_pty)
                        out_from_pty(buf, num_read);
                }
            }
        }
    }
    close(kq);
    return 0;
}
#endif

void input_into_pty(std::string str)
{
    input_into_pty(str.c_str(), str.length());
}
void input_into_pty(const char *str, int length)
{
    if (length == 0)
    {
        return;
    }
    write(fd, str, length);
    write(fd, "\n", 1);
}

void input_into_pty_no_newline(std::string str)
{
    input_into_pty_no_newline(str.c_str(), str.length());
}
void input_into_pty_no_newline(const char *str, int len)
{
    if (len == 0)
    {
        return;
    }
    write(fd, str, len);
}

void close_pty()
{
    kill(pid, SIGTERM);
    close(fd);
    if (_reading_threading.joinable())
        _reading_threading.join();
}

bool check_subprocess()
{
    int retcode = waitpid(pid, NULL, WNOHANG);
    if (retcode == pid)
    {
        close(fd);
        if (_reading_threading.joinable())
            _reading_threading.join();
        return true;
    }
    return false;
}

#if TESTSUIT == TESTSUIT_PTY
using namespace std;
int main(int argn, char *argv[])
{
    start_pty([](std::string str)
              { std::cout << str; });
    const char *strs[] = {
        "ls",
        "pwd",
        "cd ..",
#if __linux__
        "top -d 1",
#else
        "top",
#endif
        "q",
        "pwd",
        "stty -a",
        "ls -al",
    };
    int i = 0;
    int n = sizeof(strs) / sizeof(strs[0]);
    set_pty_echo(true);
    while (true)
    {
        if (check_subprocess())
        {
            break;
        }
        std::cout << "input:" << strs[i] << std::endl;
        input_into_pty(string(strs[i++]));
        sleep(1);
        if (i == 3)
        {
            struct winsize w;
            ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
            set_pty_size(w.ws_row, w.ws_col);
        }
        if (i == 4)
        {
            set_pty_echo(false);
            sleep(10);
        }
        if (i >= n)
        {
            std::string str;
            std::getline(std::cin, str);
            input_into_pty(str);
            sleep(1);
            break;
        }
    }
    close_pty();
    return 0;
}
#endif